// @ts-nocheck
// https://github.com/scttcper/tinycolor/tree/master/src
// https://github.com/ant-design/ant-design-colors/blob/master/src/generate.ts
export const namedColors = {
	aliceblue: 'f0f8ff',
	antiquewhite: 'faebd7',
	aqua: '0ff',
	aquamarine: '7fffd4',
	azure: 'f0ffff',
	beige: 'f5f5dc',
	bisque: 'ffe4c4',
	black: '000',
	blanchedalmond: 'ffebcd',
	blue: '00f',
	blueviolet: '8a2be2',
	brown: 'a52a2a',
	burlywood: 'deb887',
	burntsienna: 'ea7e5d',
	cadetblue: '5f9ea0',
	chartreuse: '7fff00',
	chocolate: 'd2691e',
	coral: 'ff7f50',
	cornflowerblue: '6495ed',
	cornsilk: 'fff8dc',
	crimson: 'dc143c',
	cyan: '0ff',
	darkblue: '00008b',
	darkcyan: '008b8b',
	darkgoldenrod: 'b8860b',
	darkgray: 'a9a9a9',
	darkgreen: '006400',
	darkgrey: 'a9a9a9',
	darkkhaki: 'bdb76b',
	darkmagenta: '8b008b',
	darkolivegreen: '556b2f',
	darkorange: 'ff8c00',
	darkorchid: '9932cc',
	darkred: '8b0000',
	darksalmon: 'e9967a',
	darkseagreen: '8fbc8f',
	darkslateblue: '483d8b',
	darkslategray: '2f4f4f',
	darkslategrey: '2f4f4f',
	darkturquoise: '00ced1',
	darkviolet: '9400d3',
	deeppink: 'ff1493',
	deepskyblue: '00bfff',
	dimgray: '696969',
	dimgrey: '696969',
	dodgerblue: '1e90ff',
	firebrick: 'b22222',
	floralwhite: 'fffaf0',
	forestgreen: '228b22',
	fuchsia: 'f0f',
	gainsboro: 'dcdcdc',
	ghostwhite: 'f8f8ff',
	gold: 'ffd700',
	goldenrod: 'daa520',
	gray: '808080',
	green: '008000',
	greenyellow: 'adff2f',
	grey: '808080',
	honeydew: 'f0fff0',
	hotpink: 'ff69b4',
	indianred: 'cd5c5c',
	indigo: '4b0082',
	ivory: 'fffff0',
	khaki: 'f0e68c',
	lavender: 'e6e6fa',
	lavenderblush: 'fff0f5',
	lawngreen: '7cfc00',
	lemonchiffon: 'fffacd',
	lightblue: 'add8e6',
	lightcoral: 'f08080',
	lightcyan: 'e0ffff',
	lightgoldenrodyellow: 'fafad2',
	lightgray: 'd3d3d3',
	lightgreen: '90ee90',
	lightgrey: 'd3d3d3',
	lightpink: 'ffb6c1',
	lightsalmon: 'ffa07a',
	lightseagreen: '20b2aa',
	lightskyblue: '87cefa',
	lightslategray: '789',
	lightslategrey: '789',
	lightsteelblue: 'b0c4de',
	lightyellow: 'ffffe0',
	lime: '0f0',
	limegreen: '32cd32',
	linen: 'faf0e6',
	magenta: 'f0f',
	maroon: '800000',
	mediumaquamarine: '66cdaa',
	mediumblue: '0000cd',
	mediumorchid: 'ba55d3',
	mediumpurple: '9370db',
	mediumseagreen: '3cb371',
	mediumslateblue: '7b68ee',
	mediumspringgreen: '00fa9a',
	mediumturquoise: '48d1cc',
	mediumvioletred: 'c71585',
	midnightblue: '191970',
	mintcream: 'f5fffa',
	mistyrose: 'ffe4e1',
	moccasin: 'ffe4b5',
	navajowhite: 'ffdead',
	navy: '000080',
	oldlace: 'fdf5e6',
	olive: '808000',
	olivedrab: '6b8e23',
	orange: 'ffa500',
	orangered: 'ff4500',
	orchid: 'da70d6',
	palegoldenrod: 'eee8aa',
	palegreen: '98fb98',
	paleturquoise: 'afeeee',
	palevioletred: 'db7093',
	papayawhip: 'ffefd5',
	peachpuff: 'ffdab9',
	peru: 'cd853f',
	pink: 'ffc0cb',
	plum: 'dda0dd',
	powderblue: 'b0e0e6',
	purple: '800080',
	rebeccapurple: '663399',
	red: 'f00',
	rosybrown: 'bc8f8f',
	royalblue: '4169e1',
	saddlebrown: '8b4513',
	salmon: 'fa8072',
	sandybrown: 'f4a460',
	seagreen: '2e8b57',
	seashell: 'fff5ee',
	sienna: 'a0522d',
	silver: 'c0c0c0',
	skyblue: '87ceeb',
	slateblue: '6a5acd',
	slategray: '708090',
	slategrey: '708090',
	snow: 'fffafa',
	springgreen: '00ff7f',
	steelblue: '4682b4',
	tan: 'd2b48c',
	teal: '008080',
	thistle: 'd8bfd8',
	tomato: 'ff6347',
	turquoise: '40e0d0',
	violet: 'ee82ee',
	wheat: 'f5deb3',
	white: 'fff',
	whitesmoke: 'f5f5f5',
	yellow: 'ff0',
	yellowgreen: '9acd32'
}
//
// // amt > 0 darken, amt < 0 lighten
// export function darken(col, amt) {
//   let usePound = false
//
//   if(col[0] === '#') {
//     col = col.slice(1)
//     usePound = true
//   }
//
//   const num = parseInt(col, 16)
//
//   let r = (num >> 16) + amt
//
//   if(r > 255) r = 255
//   else if(r < 0) r = 0
//
//   let b = ((num >> 8) & 0x00FF) + amt
//
//   if(b > 255) b = 255
//   else if(b < 0) b = 0
//
//   let g = (num & 0x0000FF) + amt
//
//   if(g > 255) g = 255
//   else if(g < 0) g = 0
//
//   return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16)
// }

// Wrapper function ported from TinyColor.prototype.mix, not treeshakable.
// Assume color1 & color2 has no alpha, since the following src code did so.
// Amount in range [0, 1]
export function mix(rgb1, rgb2, amount) {
	const r1 = bound01(rgb1.r, 255)
	const g1 = bound01(rgb1.g, 255)
	const b1 = bound01(rgb1.b, 255)
	const r2 = bound01(rgb2.r, 255)
	const g2 = bound01(rgb2.g, 255)
	const b2 = bound01(rgb2.b, 255)
	return {
		r: Math.round(((r2 - r1) * amount + r1) * 255),
		g: Math.round(((g2 - g1) * amount + g1) * 255),
		b: Math.round(((b2 - b1) * amount + b1) * 255)
	}
}

export function darken(color, amount): string {
	console.log(`color = ${color}`)
	console.log(`colorToRGB(color) = `, colorToRGB(color))
	const rgb = mix(colorToRGB(color), {r: 0, g: 0, b: 0}, amount)
	console.log(`rgb = `, rgb)
	return rgbToHex(rgb.r, rgb.g, rgb.r, false)
}

// Utilities
// { 'name1': 'val1' } becomes { 'val1': 'name1' }
const flip = function (o) {
	const flipped = {}
	for (const i in o) {
		if (o.hasOwnProperty(i)) {
			flipped[o[i]] = i
		}
	}
	return flipped
}

// Return a valid alpha value [0,1] with all invalid values being set to 1
const boundAlpha = function (a) {
	a = parseFloat(a)

	if (isNaN(a) || a < 0 || a > 1) {
		a = 1
	}

	return a
}

// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
const isOnePointZero = function (n) {
	return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1
}

const isPercentage = function (n) {
	return typeof n === 'string' && n.indexOf('%') !== -1
}

// Take input from [0, n] and return it as [0, 1]
const bound01 = function (value, max) {
	if (isOnePointZero(value)) value = '100%'

	const processPercent = isPercentage(value)
	value = Math.min(max, Math.max(0, parseFloat(value)))

	// Automatically convert percentage into number
	if (processPercent) {
		value = parseInt(value * max, 10) / 100
	}

	// Handle floating point rounding errors
	if (Math.abs(value - max) < 0.000001) {
		return 1
	}

	// Convert into [0, 1] range if it isn't already
	return (value % max) / parseFloat(max)
}

const pad2 = function (c) {
	return c.length == 1 ? '0' + c : '' + c
}

// Force a number between 0 and 1
const clamp01 = function (val) {
	return Math.min(1, Math.max(0, val))
}

// Parse a base-16 hex value into a base-10 integer
const parseIntFromHex = function (val) {
	return parseInt(val, 16)
}

// Replace a decimal with it's percentage value
const convertToPercentage = function (n) {
	if (n <= 1) {
		n = (n * 100) + '%'
	}
	return n
}

// Converts a decimal to a hex value
const convertDecimalToHex = function (d) {
	return Math.round(parseFloat(d) * 255).toString(16)
}

// Converts a hex value to a decimal
const convertHexToDecimal = function (h) {
	return (parseIntFromHex(h) / 255)
}

// Make it easy to access colors via hexNames[hex]
const hexNames = flip(namedColors)

const matchers = (function () {

	// <http://www.w3.org/TR/css3-values/#integers>
	const CSS_INTEGER = '[-\\+]?\\d+%?'

	// <http://www.w3.org/TR/css3-values/#number-value>
	const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?'

	// Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
	const CSS_UNIT = '(?:' + CSS_NUMBER + ')|(?:' + CSS_INTEGER + ')'

	// Actual matching.
	// Parentheses and commas are optional, but not required.
	// Whitespace can take the place of commas or opening paren
	const PERMISSIVE_MATCH3 = '[\\s|\\(]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')\\s*\\)?'
	const PERMISSIVE_MATCH4 = '[\\s|\\(]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')\\s*\\)?'

	return {
		CSS_UNIT: new RegExp(CSS_UNIT),
		rgb: new RegExp('rgb' + PERMISSIVE_MATCH3),
		rgba: new RegExp('rgba' + PERMISSIVE_MATCH4),
		hsl: new RegExp('hsl' + PERMISSIVE_MATCH3),
		hsla: new RegExp('hsla' + PERMISSIVE_MATCH4),
		hsv: new RegExp('hsv' + PERMISSIVE_MATCH3),
		hsva: new RegExp('hsva' + PERMISSIVE_MATCH4),
		hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
		hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
		hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
		hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
	}
})()

// isValidCSSUnit
// Take in a single string / number and check to see if it looks like a CSS unit
// (see matchers above for definition).
const isValidCSSUnit = function (color) {
	return !!matchers.CSS_UNIT.exec(color)
}

// stringInputToObject
// Permissive string parsing.  Take in a number of formats, and output an object
// based on detected format.  Returns { r, g, b } or { h, s, l } or { h, s, v}
export function colorToObject(color) {
	const trimLeft = /^\s+/, trimRight = /\s+$/
	color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase()
	let named = false
	if (namedColors[color]) {
		color = namedColors[color]
		named = true
	} else if (color == 'transparent') {
		return {r: 0, g: 0, b: 0, a: 0, format: 'name'}
	}

	// Try to match string input using regular expressions.
	// Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
	// Just return an object and let the conversion functions handle that.
	// This way the result will be the same whether the tinycolor is initialized with string or object.
	let match
	if ((match = matchers.rgb.exec(color))) {
		return {r: match[1], g: match[2], b: match[3]}
	} else if ((match = matchers.rgba.exec(color))) {
		return {r: match[1], g: match[2], b: match[3], a: match[4]}
	} else if ((match = matchers.hsl.exec(color))) {
		return {h: match[1], s: match[2], l: match[3]}
	} else if ((match = matchers.hsla.exec(color))) {
		return {h: match[1], s: match[2], l: match[3], a: match[4]}
	} else if ((match = matchers.hsv.exec(color))) {
		return {h: match[1], s: match[2], v: match[3]}
	} else if ((match = matchers.hsva.exec(color))) {
		return {h: match[1], s: match[2], v: match[3], a: match[4]}
	} else if ((match = matchers.hex8.exec(color))) {
		return {
			r: parseIntFromHex(match[1]),
			g: parseIntFromHex(match[2]),
			b: parseIntFromHex(match[3]),
			a: convertHexToDecimal(match[4]),
			format: named ? 'name' : 'hex8'
		}
	} else if ((match = matchers.hex6.exec(color))) {
		return {
			r: parseIntFromHex(match[1]),
			g: parseIntFromHex(match[2]),
			b: parseIntFromHex(match[3]),
			format: named ? 'name' : 'hex'
		}
	} else if ((match = matchers.hex4.exec(color))) {
		return {
			r: parseIntFromHex(match[1] + '' + match[1]),
			g: parseIntFromHex(match[2] + '' + match[2]),
			b: parseIntFromHex(match[3] + '' + match[3]),
			a: convertHexToDecimal(match[4] + '' + match[4]),
			format: named ? 'name' : 'hex8'
		}
	} else if ((match = matchers.hex3.exec(color))) {
		return {
			r: parseIntFromHex(match[1] + '' + match[1]),
			g: parseIntFromHex(match[2] + '' + match[2]),
			b: parseIntFromHex(match[3] + '' + match[3]),
			format: named ? 'name' : 'hex'
		}
	}

	return false
}

// Given a string or object, convert that input to RGB
// Possible string inputs:
//
//     "red"
//     "#f00" or "f00"
//     "#ff0000" or "ff0000"
//     "#ff000000" or "ff000000"
//     "rgb 255 0 0" or "rgb (255, 0, 0)"
//     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
//     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
//     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
//     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
//     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
//     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
//
export function colorToRGB(color) {

	let rgb = {r: 0, g: 0, b: 0}
	let a = 1
	let s = null
	let v = null
	let l = null
	let ok = false
	let format = false

	if (typeof color == 'string') {
		color = colorToObject(color)
	}

	if (typeof color == 'object') {
		if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
			rgb = rgbToRgb(color.r, color.g, color.b)
			ok = true
			format = String(color.r).substr(-1) === '%' ? 'prgb' : 'rgb'
		} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
			s = convertToPercentage(color.s)
			v = convertToPercentage(color.v)
			rgb = hsvToRgb(color.h, s, v)
			ok = true
			format = 'hsv'
		} else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
			s = convertToPercentage(color.s)
			l = convertToPercentage(color.l)
			rgb = hslToRgb(color.h, s, l)
			ok = true
			format = 'hsl'
		}

		if (color.hasOwnProperty('a')) {
			a = color.a
		}
	}

	return {
		ok: ok,
		format: color.format || format,
		r: Math.min(255, Math.max(rgb.r, 0)),
		g: Math.min(255, Math.max(rgb.g, 0)),
		b: Math.min(255, Math.max(rgb.b, 0)),
		a: boundAlpha(a)
	}
}

// rgbToRgb
// Handle bounds / percentage checking to conform to CSS color spec
// <http://www.w3.org/TR/css3-color/>
// *Assumes:* r, g, b in [0, 255] or [0, 1]
// *Returns:* { r, g, b } in [0, 255]
export function rgbToRgb(r, g, b) {
	if (typeof r === 'object') {
		b = r.b
		g = r.g
		r = r.r
	}
	return {
		r: bound01(r, 255) * 255,
		g: bound01(g, 255) * 255,
		b: bound01(b, 255) * 255
	}
}

// `rgbToHsv`
// Converts an RGB color value to HSV
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
// *Returns:* { h, s, v } in hsv(360, 100, 100)
export function rgbToHsv(r, g, b) {
	if (typeof r === 'object') {
		b = r.b
		g = r.g
		r = r.r
	}

	r = bound01(r, 255)
	g = bound01(g, 255)
	b = bound01(b, 255)

	const max = Math.max(r, g, b)
	const min = Math.min(r, g, b)
	let h, s
	let v = max

	const d = max - min
	s = max === 0 ? 0 : d / max

	if (max === min) {
		h = 0 // achromatic
	} else {
		switch (max) {
			case r:
				h = (g - b) / d + (g < b ? 6 : 0)
				break
			case g:
				h = (b - r) / d + 2
				break
			case b:
				h = (r - g) / d + 4
				break
		}
		h /= 6
	}

	return {
		h: Math.round(h * 360),
		s: Math.round(s * 100),
		v: Math.round(v * 100)
	}
}

// `hsvToRgb`
// Converts an HSV color value to RGB.
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
// *Returns:* { r, g, b } in the set [0, 255]
export function hsvToRgb(h, s, v) {
	if (typeof h === 'object') {
		v = h.v
		s = h.s
		h = h.h
	}
	h = bound01(h, 360) * 6
	s = bound01(s, 100)
	v = bound01(v, 100)

	const i = Math.floor(h)
	const f = h - i
	const p = v * (1 - s)
	const q = v * (1 - f * s)
	const t = v * (1 - (1 - f) * s)
	const mod = i % 6
	const r = [v, q, p, p, t, v][mod]
	const g = [t, v, v, q, p, p][mod]
	const b = [p, p, t, v, v, q][mod]

	return {
		r: Math.round(r * 255),
		g: Math.round(g * 255),
		b: Math.round(b * 255)
	}
}

export function hsvToHsl(h, s, v) {
	if (typeof h === 'object') {
		v = h.v
		s = h.s
		h = h.h
	}
	let sl, l
	l = (2 - s) * v
	sl = s * v
	sl /= l < 1 ? l : 2 - l
	sl = sl || 0
	l /= 2
	return [h, sl, l]
}

export function hslToHsv(h, s, l) {
	if (typeof h === 'object') {
		l = h.l
		s = h.s
		h = h.h
	}
	s = s / 100
	l = l / 100
	let sMin = s
	const lMin = Math.max(l, 0.01)
	let sv
	let v

	l *= 2
	s *= l <= 1 ? l : 2 - l
	sMin *= lMin <= 1 ? lMin : 2 - lMin
	v = (l + s) / 2
	sv = l === 0 ? 2 * sMin / (lMin + sMin) : 2 * s / (l + s)

	return {
		h: h,
		s: sv * 100,
		v: v * 100
	}
}

// rgbToHsl
// Converts an RGB color value to HSL.
// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
// *Returns:* { h, s, l } in [0,1]
export function rgbToHsl(r, g, b) {
	if (typeof r === 'object') {
		b = r.b
		g = r.g
		r = r.r
	}

	r = bound01(r, 255)
	g = bound01(g, 255)
	b = bound01(b, 255)

	const max = Math.max(r, g, b), min = Math.min(r, g, b)
	let h, s, l = (max + min) / 2

	if (max === min) {
		h = s = 0 // achromatic
	} else {
		const d = max - min
		s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
		switch (max) {
			case r:
				h = (g - b) / d + (g < b ? 6 : 0)
				break
			case g:
				h = (b - r) / d + 2
				break
			case b:
				h = (r - g) / d + 4
				break
		}

		h /= 6
	}

	return {
		h: Math.round(h * 360),
		s: Math.round(s * 100),
		v: Math.round(v * 100)
	}
}

export function hslToRgb(h, s, l) {
	if (typeof h === 'object') {
		l = h.l
		s = h.s
		h = h.h
	}

	h = bound01(h, 360)
	s = bound01(s, 100)
	l = bound01(l, 100)

	function hue2rgb(p, q, t) {
		if (t < 0) t += 1
		if (t > 1) t -= 1
		if (t < 1 / 6) return p + (q - p) * 6 * t
		if (t < 1 / 2) return q
		if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
		return p
	}

	let r, g, b
	if (s === 0) {
		r = g = b = l // achromatic
	} else {
		const q = l < 0.5 ? l * (1 + s) : l + s - l * s
		const p = 2 * l - q
		r = hue2rgb(p, q, h + 1 / 3)
		g = hue2rgb(p, q, h)
		b = hue2rgb(p, q, h - 1 / 3)
	}

	return {
		r: Math.round(r * 255),
		g: Math.round(g * 255),
		b: Math.round(b * 255)
	}
}

// rgbToHex
// Converts an RGB color to hex
// Assumes r, g, and b are contained in the set [0, 255]
// Returns a 3 or 6 character hex
export function rgbToHex(r, g, b, allow3Char = true) {
	if (typeof r === 'object') {
		allow3Char = g
		b = r.b
		g = r.g
		r = r.r
	}
	const hex = [
		pad2(Math.round(r).toString(16)),
		pad2(Math.round(g).toString(16)),
		pad2(Math.round(b).toString(16))
	]

	// Return a 3 character hex if possible
	if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
		return '#' + hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0)
	}

	return '#' + hex.join('')
}

// rgbaToHex
// Converts an RGBA color plus alpha transparency to hex
// Assumes r, g, b are contained in the set [0, 255] and
// a in [0, 1]. Returns a 4 or 8 character rgba hex
export function rgbaToHex(r, g, b, a, allow4Char = true) {
	if (typeof r === 'object') {
		allow4Char = g
		a = r.a
		b = r.b
		g = r.g
		r = r.r
	}
	const hex = [
		pad2(Math.round(r).toString(16)),
		pad2(Math.round(g).toString(16)),
		pad2(Math.round(b).toString(16)),
		pad2(convertDecimalToHex(a))
	]

	// Return a 4 character hex if possible
	if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) {
		return '#' + hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0)
	}

	return '#' + hex.join('')
}

// rgbaToArgbHex
// Converts an RGBA color to an ARGB Hex8 string
// Rarely used, but required for "toFilter()"
export function rgbaToArgbHex(r, g, b, a) {
	if (typeof r === 'object') {
		a = r.a
		b = r.b
		g = r.g
		r = r.r
	}
	const hex = [
		pad2(convertDecimalToHex(a)),
		pad2(Math.round(r).toString(16)),
		pad2(Math.round(g).toString(16)),
		pad2(Math.round(b).toString(16))
	]
	return '#' + hex.join('')
}

// const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' }
// const hexOne = function(value) {
//   value = Math.min(Math.round(value), 255)
//   const high = Math.floor(value / 16)
//   const low = value % 16
//   return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low)
// }
//
// export function rgbToHex(r, g, b) {
//   if(isNaN(r) || isNaN(g) || isNaN(b)) return ''
//   return '#' + hexOne(r) + hexOne(g) + hexOne(b)
// }

const HEX_INT_MAP = {A: 10, B: 11, C: 12, D: 13, E: 14, F: 15}

const parseHexChannel = function (hex) {
	if (hex.length === 2) {
		return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 +
			(HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1])
	}
	return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]
}

// colorPalette
const hueStep = 2 // 色相阶梯
const saturationStep = 16 // 饱和度阶梯，浅色部分
const saturationStep2 = 5 // 饱和度阶梯，深色部分
const brightnessStep1 = 5 // 亮度阶梯，浅色部分
const brightnessStep2 = 15 // 亮度阶梯，深色部分
const lightColorCount = 5 // 浅色数量，主色上
const darkColorCount = 4 // 深色数量，主色下
// 暗色主题颜色映射关系表
const darkColorMap = [
	{index: 7, opacity: 0.15},
	{index: 6, opacity: 0.25},
	{index: 5, opacity: 0.3},
	{index: 5, opacity: 0.45},
	{index: 5, opacity: 0.65},
	{index: 5, opacity: 0.85},
	{index: 4, opacity: 0.9},
	{index: 3, opacity: 0.95},
	{index: 2, opacity: 0.97},
	{index: 1, opacity: 0.98},
]

const getHue = function (hsv, i, isLight) {
	let hue
	// 根据色相不同，色相转向不同
	if (hsv.h >= 60 && hsv.h <= 240) {
		hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i
	} else {
		hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i
	}
	if (hue < 0) {
		hue += 360
	} else if (hue >= 360) {
		hue -= 360
	}
	return Math.round(hue)
}

const getSaturation = function (hsv, i, isLight) {
	// grey color don't change saturation
	if (hsv.h === 0 && hsv.s === 0) {
		return hsv.s
	}
	let saturation
	if (isLight) {
		saturation = hsv.s - saturationStep * i
	} else if (i === darkColorCount) {
		saturation = hsv.s + saturationStep
	} else {
		saturation = hsv.s + saturationStep2 * i
	}
	if (saturation > 100) {
		saturation = 100
	}
	if (isLight && i === lightColorCount && saturation > 10) {
		saturation = 10
	}
	if (saturation < 6) {
		saturation = 6
	}
	return Math.round(saturation)
}

const getValue = function (hsv, i, isLight) {
	let value
	if (isLight) {
		value = hsv.v + brightnessStep1 * i
	} else {
		value = hsv.v - brightnessStep2 * i
	}
	if (value > 100) {
		value = 100
	}
	return Math.round(value)
}

export function colorPalette(color, index) {
	const isLight = index <= 6
	const rgb = colorToRGB(color)
	const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b)
	const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1

	const h = getHue(hsv, i, isLight)
	const s = getSaturation(hsv, i, isLight)
	const v = getValue(hsv, i, isLight)

	const newRgb = hsvToRgb(h, s, v)
	return rgbToHex(newRgb.r, newRgb.g, newRgb.b)
}

export function generateColors(color, opts) {
	const patterns = []
	const pColor = colorToRGB(color)
	for (let i = lightColorCount; i > 0; i -= 1) {
		const hsv = rgbToHsv(pColor)
		const colorString = rgbToHex(
			colorToRGB({
				h: getHue(hsv, i, true),
				s: getSaturation(hsv, i, true),
				v: getValue(hsv, i, true),
			})
		)
		patterns.push(colorString)
	}
	patterns.push(rgbToHex(pColor))
	for (let i = 1; i <= darkColorCount; i += 1) {
		const hsv = rgbToHsv(pColor)
		const colorString = rgbToHex(
			colorToRGB({
				h: getHue(hsv, i),
				s: getSaturation(hsv, i),
				v: getValue(hsv, i),
			})
		)
		patterns.push(colorString)
	}

	// dark theme patterns
	if (opts && opts.theme === 'dark') {
		return darkColorMap.map(({index, opacity}) => {
			const darkColorString = rgbToHex(
				mix(
					colorToRGB(opts.backgroundColor || '#141414'),
					colorToRGB(patterns[index]),
					opacity * 100
				),
			)
			return darkColorString
		})
	}
	return patterns
}

// https://ant.design/docs/spec/colors-cn
// https://antv.vision/zh/docs/specification/language/palette
export const presetPrimaryColors = [
	{color: '#F5222D', name: 'red', title: '薄暮', desc: '斗志、奔放'},
	{color: '#FA541C', name: 'volcano', title: '火山', desc: '醒目、澎湃'},
	{color: '#FA8C16', name: 'orange', title: '日暮', desc: '温暖、欢快'},
	{color: '#FAAD14', name: 'gold', title: '金盏花', desc: '活力、积极'},
	{color: '#FADB14', name: 'yellow', title: '日出', desc: '出生、阳光'},
	{color: '#A0D911', name: 'lime', title: '青柠', desc: '自然、生机'},
	{color: '#52C41A', name: 'green', title: '极光绿', desc: '健康、创新'},
	{color: '#13C2C2', name: 'cyan', title: '明青', desc: '希望、坚强'},
	{color: '#1890FF', name: 'blue', title: '拂晓蓝', desc: '包容、科技、普惠'},
	{color: '#2F54EB', name: 'geekblue', title: '极客蓝', desc: '探索、钻研'},
	{color: '#722ED1', name: 'purple', title: '酱紫', desc: '优雅、浪漫'},
	{color: '#EB2F96', name: 'magenta', title: '法式洋红', desc: '明快、感性'},
	{color: '#666666', name: 'grey', title: '灰色', desc: '灰色'},
]

export const presetPalettes = {}
export const presetDarkPalettes = {}
presetPrimaryColors.forEach(({name, color}, index) => {
	// console.log(`generateColors(${color}) => `, generateColors(color))
	presetPrimaryColors[index].paletteColors = presetPalettes[name] = generateColors(color)
	// presetPalettes[name].primary = presetPalettes[name][5]

	// dark presetPalettes
	presetPrimaryColors[index].darkPaletteColors = presetDarkPalettes[name] = generateColors(color, {
		theme: 'dark',
		backgroundColor: '#141414',
	})
	// presetDarkPalettes[name].primary = presetDarkPalettes[name][5]
})

export const red = presetPalettes.red
export const volcano = presetPalettes.volcano
export const gold = presetPalettes.gold
export const orange = presetPalettes.orange
export const yellow = presetPalettes.yellow
export const lime = presetPalettes.lime
export const green = presetPalettes.green
export const cyan = presetPalettes.cyan
export const blue = presetPalettes.blue
export const geekblue = presetPalettes.geekblue
export const purple = presetPalettes.purple
export const magenta = presetPalettes.magenta
export const grey = presetPalettes.grey

export class Color {
	constructor(options) {
		this._hue = 0
		this._saturation = 100
		this._value = 100
		this._alpha = 100

		this.enableAlpha = false
		this.format = 'hex'
		this.value = ''

		options = options || {}

		for (let option in options) {
			if (options.hasOwnProperty(option)) {
				this[option] = options[option]
			}
		}

		this.doOnChange()
	}

	set(prop, value) {
		if (arguments.length === 1 && typeof prop === 'object') {
			for (const p in prop) {
				if (prop.hasOwnProperty(p)) {
					this.set(p, prop[p])
				}
			}
			return
		}

		this['_' + prop] = value
		this.doOnChange()
	}

	get(prop) {
		return this['_' + prop]
	}

	toRgb() {
		return hsvToRgb(this._hue, this._saturation, this._value)
	}

	toHex() {
		const rgb = hsvToRgb(this._hue, this._saturation, this._value)
		return rgbToHex(rgb.r, rgb.g, rgb.b)
	}

	fromString(value) {
		if (!value) {
			this._hue = 0
			this._saturation = 100
			this._value = 100
			this._alpha = 100

			this.doOnChange()
			return
		}

		const fromHSV = (h, s, v) => {
			this._hue = h
			this._saturation = s
			this._value = v

			this.doOnChange()
		}

		if (value.indexOf('hsl') !== -1) {
			const parts = value
				.replace(/hsla|hsl|\(|\)/gm, '')
				.split(/\s|,/g)
				.filter(val => val !== '')
				.map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))

			if (parts.length === 4) {
				this._alpha = Math.floor(parseFloat(parts[3]) * 100)
			} else {
				this._alpha = 100
			}

			if (parts.length >= 3) {
				const {h, s, v} = hslToHsv(parts[0], parts[1], parts[2])
				fromHSV(h, s, v)
			}
		} else if (value.indexOf('hsv') !== -1) {
			const parts = value
				.replace(/hsva|hsv|\(|\)/gm, '')
				.split(/\s|,/g)
				.filter(val => val !== '')
				.map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))

			if (parts.length === 4) {
				this._alpha = Math.floor(parseFloat(parts[3]) * 100)
			} else {
				this._alpha = 100
			}

			if (parts.length >= 3) {
				fromHSV(parts[0], parts[1], parts[2])
			}
		} else if (value.indexOf('rgb') !== -1) {
			const parts = value
				.replace(/rgba|rgb|\(|\)/gm, '')
				.split(/\s|,/g)
				.filter(val => val !== '')
				.map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10))

			if (parts.length === 4) {
				this._alpha = Math.floor(parseFloat(parts[3]) * 100)
			} else {
				this._alpha = 100
			}
			if (parts.length >= 3) {
				const {h, s, v} = rgbToHsv(parts[0], parts[1], parts[2])
				fromHSV(h, s, v)
			}
		} else if (value.indexOf('#') !== -1) {
			const hex = value.replace('#', '').trim()
			let r, g, b

			if (hex.length === 3) {
				r = parseHexChannel(hex[0] + hex[0])
				g = parseHexChannel(hex[1] + hex[1])
				b = parseHexChannel(hex[2] + hex[2])
			} else if (hex.length === 6 || hex.length === 8) {
				r = parseHexChannel(hex.substring(0, 2))
				g = parseHexChannel(hex.substring(2, 4))
				b = parseHexChannel(hex.substring(4, 6))
			}

			if (hex.length === 8) {
				this._alpha = Math.floor(parseHexChannel(hex.substring(6, 8)) / 256 * 100)
			} else {
				this._alpha = 100
			}

			const {h, s, v} = rgbToHsv(r, g, b)
			fromHSV(h, s, v)
		}
	}

	doOnChange() {
		const {_hue, _saturation, _value, _alpha, format} = this

		if (this.enableAlpha) {
			switch (format) {
				case 'hsl': {
					const hsl = hsvToHsl(_hue, _saturation / 100, _value / 100)
					this.value = `hsla(${_hue}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%, ${_alpha / 100})`
					break
				}
				case 'hsv':
					this.value = `hsva(${_hue}, ${Math.round(_saturation)}%, ${Math.round(_value)}%, ${_alpha / 100})`
					break
				default: {
					const {r, g, b} = hsvToRgb(_hue, _saturation, _value)
					this.value = `rgba(${r}, ${g}, ${b}, ${_alpha / 100})`
				}
			}
		} else {
			switch (format) {
				case 'hsl': {
					const hsl = hsvToHsl(_hue, _saturation / 100, _value / 100)
					this.value = `hsl(${_hue}, ${Math.round(hsl[1] * 100)}%, ${Math.round(hsl[2] * 100)}%)`
					break
				}
				case 'hsv':
					this.value = `hsv(${_hue}, ${Math.round(_saturation)}%, ${Math.round(_value)}%)`
					break
				case 'rgb': {
					const {r, g, b} = hsvToRgb(_hue, _saturation, _value)
					this.value = `rgb(${r}, ${g}, ${b})`
					break
				}
				default:
					const rgb = hsvToRgb(_hue, _saturation, _value)
					this.value = rgbToHex(rgb.r, rgb.g, rgb.b)
			}
		}
	}
}
